package com.zh.blog.service.impl;

import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zh.blog.common.redis.RedisService;
import com.zh.blog.dao.UserAuthDao;
import com.zh.blog.dao.UserInfoDao;
import com.zh.blog.dao.UserRoleDao;
import com.zh.blog.dto.EmailDto;
import com.zh.blog.dto.UserAreaDto;
import com.zh.blog.dto.UserBackDto;
import com.zh.blog.dto.UserInfoDto;
import com.zh.blog.enums.LoginTypeEnum;
import com.zh.blog.enums.RoleEnum;
import com.zh.blog.exception.BizException;
import com.zh.blog.model.UserAuth;
import com.zh.blog.model.UserInfo;
import com.zh.blog.model.UserRole;
import com.zh.blog.security.component.UserUtils;
import com.zh.blog.service.BlogInfoService;
import com.zh.blog.service.UserAuthService;
import com.zh.blog.strategy.context.SocialLoginStrategyContext;
import com.zh.blog.util.PageUtils;
import com.zh.blog.vo.*;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.security.crypto.bcrypt.BCrypt;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import static com.zh.blog.constant.CommonConst.*;
import static com.zh.blog.constant.MQPrefixConst.EMAIL_EXCHANGE;
import static com.zh.blog.constant.RedisPrefixConst.*;
import static com.zh.blog.enums.UserAreaTypeEnum.getUserAreaType;
import static com.zh.blog.util.CommonUtils.checkEmail;
import static com.zh.blog.util.CommonUtils.getRandomSCode;

/**
 * @author ZhengHao
 * Created At 2022/8/2.
 **/
@Service
public class UserAuthServiceImpl extends ServiceImpl<UserAuthDao, UserAuth> implements UserAuthService {
    @Autowired
    private RedisService redisService;
    @Autowired
    private UserAuthDao userAuthDao;
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private BlogInfoService blogInfoService;
    @Autowired
    private UserInfoDao userInfoDao;
    @Autowired
    private UserRoleDao userRoleDao;
    @Autowired
    private SocialLoginStrategyContext socialLoginStrategyContext;

    @Override
    public List<UserAreaDto> listUsersArea(ConditionVO conditionVO) {
        List<UserAreaDto> userAreaDtoList = new ArrayList<>();
        switch (Objects.requireNonNull(getUserAreaType(conditionVO.getType()))) {
            case USER:
                // 查询注册用户区域分布
                Object userArea = redisService.get(USER_AREA);
                if (Objects.nonNull(userArea)) {
                    userAreaDtoList = JSON.parseObject(userArea.toString(), List.class);
                }
                return userAreaDtoList;
            case VISITOR:
                // 查询游客区域分布
                Map<String, Object> visitorArea = redisService.hGetAll(VISITOR_AREA);
                if (Objects.nonNull(visitorArea)) {
                    userAreaDtoList = visitorArea.entrySet().stream()
                            .map(item -> UserAreaDto.builder()
                            .name(item.getKey())
                            .value(Long.valueOf(item.getValue().toString()))
                            .build())
                    .collect(Collectors.toList());
                }
                return userAreaDtoList;
            default:
                break;
        }
        return userAreaDtoList;
    }

    @Override
    public PageResult<UserBackDto> listUserBackDto(ConditionVO conditionVO) {
        // 获取后台用户数量
        Long count = userAuthDao.countUsers(conditionVO);
        if (count == 0) {
            return new PageResult<>();
        }
        // 获取后台用户列表
        List<UserBackDto> userBackDtoList = userAuthDao.listUsers(PageUtils.getLimitCurrent(), PageUtils.getSize(), conditionVO);
        return new PageResult<>(userBackDtoList, count);
    }

    @Override
    public void updateAdminPassword(PasswordVO passwordVO) {
        // 获取当前登录用户信息
        UserAuth user = userAuthDao.selectOne(new LambdaQueryWrapper<UserAuth>()
                .eq(UserAuth::getId, UserUtils.getLoginUser().getId()));
        // 查询旧密码是否正确，正确则修改密码，错误则提示旧密码不正确
        if (Objects.nonNull(user) && BCrypt.checkpw(passwordVO.getOldPassword(), user.getPassword())) {
            UserAuth userAuth = UserAuth.builder()
                    .id(UserUtils.getLoginUser().getId())
                    .password(BCrypt.hashpw(passwordVO.getNewPassword(), BCrypt.gensalt()))
                    .build();
            userAuthDao.updateById(userAuth);
        } else {
            throw new BizException("旧密码不正确,请确认");
        }
    }

    @Override
    public void sendCode(String username) {
        // 检验账号是否合法
        if (!checkEmail(username)) {
            throw new BizException("请输入正确邮箱");
        }
        // 生成六位随机验证码发送
        String code = getRandomSCode();
        // 发送验证码
        EmailDto emailDto = EmailDto.builder()
                .email(username)
                .subject("验证码")
                .content("您的验证码为 " + code + " 有效期为15分钟，请不要将验证码随意告诉他人!!!")
                .build();
        rabbitTemplate.convertAndSend(EMAIL_EXCHANGE, "*" , new Message(JSON.toJSONBytes(emailDto), new MessageProperties()));
        // 将验证码存入redis,设置过期时间为15分钟
        redisService.set(USER_CODE_KEY + username, code, CODE_EXPIRE_TIME);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void register(UserVO userVO) {
        // 校验账号是否合法
        if (checkUser(userVO)) {
            throw new BizException("邮箱已被注册！！！");
        }
        // 新增用户信息
        UserInfo userInfo = UserInfo.builder()
                .email(userVO.getUsername())
                .nickname(DEFAULT_NICKNAME + IdWorker.getId())
                .avatar(blogInfoService.getWebsiteConfig().getUserAvatar())
                .build();
        userInfoDao.insert(userInfo);
        // 绑定用户角色
        UserRole userRole = UserRole.builder()
                .userId(userInfo.getId())
                .roleId(RoleEnum.USER.getRoleId())
                .build();
        userRoleDao.insert(userRole);
        // 新增用户账号
        UserAuth userAuth = UserAuth.builder()
                .userInfoId(userInfo.getId())
                .username(userVO.getUsername())
                .password(BCrypt.hashpw(userVO.getPassword(), BCrypt.gensalt()))
                .loginType(LoginTypeEnum.EMAIL.getType())
                .build();
        userAuthDao.insert(userAuth);
    }

    @Override
    public void updatePassword(UserVO user) {
        // 校验账号是否合法
        if (!checkUser(user)) {
            throw new BizException("该邮箱没有被注册！！！");
        }
        // 根据用户名修改密码
        userAuthDao.update(new UserAuth(), new LambdaUpdateWrapper<UserAuth>()
        .set(UserAuth::getPassword,BCrypt.hashpw(user.getPassword(), BCrypt.gensalt()))
        .eq(UserAuth::getUsername, user.getUsername()));
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public UserInfoDto weiboLogin(WeiboLoginVO weiboLoginVO) {
        return socialLoginStrategyContext.executeLoginStrategy(JSON.toJSONString(weiboLoginVO),LoginTypeEnum.WEIBO);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public UserInfoDto qqLogin(QQLoginVO qqLoginVO) {
        return socialLoginStrategyContext.executeLoginStrategy(JSON.toJSONString(qqLoginVO),LoginTypeEnum.QQ);
    }

    /**
     * 校验用户数据是否合法
     * @param user 用户数据
     * @return 结果
     */
    private boolean checkUser(UserVO user) {
        if (!user.getCode().equals(redisService.get(USER_CODE_KEY + user.getUsername()))) {
            throw new BizException("验证码错误");
        }
        // 查询用户名是否存在
        UserAuth userAuth = userAuthDao.selectOne(new LambdaQueryWrapper<UserAuth>()
                .select(UserAuth::getUsername)
                .eq(UserAuth::getUsername, user.getUsername()));
        return Objects.nonNull(userAuth);
    }

    /**
     * 统计用户地区
     * 通过定时器一小时访问一次数据库存入redis中
     * 用于显示用户地域分布地图
     */
    @Scheduled(cron = "0 0 * * * ?")
    public void statisticalUserArea() {
        // 统计用户地域分布
        Map<String, Long> userAreaMap = userAuthDao.selectList(new LambdaQueryWrapper<UserAuth>()
                .select(UserAuth::getIpSource))
                .stream()
                .map(item -> {
                    if (StringUtils.isNotBlank(item.getIpSource())) {
                        return item.getIpSource().substring(0, 2)
                                .replaceAll(PROVINCE, "")
                                .replaceAll(CITY, "");
                    }
                    return UNKNOWN;
                })
                .collect(Collectors.groupingBy(item -> item, Collectors.counting()));
        // 转换格式
        List<UserAreaDto> userAreaList = userAreaMap.entrySet().stream()
                .map(item -> UserAreaDto.builder()
                        .name(item.getKey())
                        .value(item.getValue())
                        .build())
                .collect(Collectors.toList());
        redisService.set(USER_AREA, JSON.toJSONString(userAreaList));


    }
}